package com.emc.esu.api.hash
{
	import com.hurlant.math.BigInteger;
	
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	/**
	 * Implements the SHA0 (FIPS 180) hashing algorithm.
	 */ 
	public class SHA0 implements MessageDigest
	{
		public static const BLOCK_SIZE:int = 64;
		internal var state:Array;
		internal var constants:Array; 
		internal var counter:Number; // # of bits hashed
		internal var buffer:ByteArray;

		public function SHA0()
		{
			state = new Array();
			constants = new Array();
			buffer = new ByteArray();
			buffer.endian = Endian.BIG_ENDIAN;
			initialize();
		}
		
		private function initialize():void {
			this.state[0] = 0x67452301;
        	this.state[1] = 0xefcdab89;
        	this.state[2] = 0x98badcfe;
        	this.state[3] = 0x10325476;
        	this.state[4] = 0xc3d2e1f0;

			this.constants[0] = 0x5a827999;
			this.constants[1] = 0x6ed9eba1;
			this.constants[2] = 0x8f1bbcdc;
			this.constants[3] = 0xca62c1d6;
			
			counter = 0;
		}
		
		/**
		 * Creates a deep copy of the object.  This allows you to get the
		 * current hash value at various offsets without disrupting the 
		 * hash in progress, e.g.
		 * <code>
		 * var sha:SHA0 = new SHA0();
		 * sha.hashUpdate( block1 );
		 * var shacopy:SHA0 = sha.clone();
		 * var partialHash:ByteArray = shacopy.hashFinal(null);
		 * sha.hashUpdate( block2 );
		 * ...
		 * </code>
		 */
		public function clone():MessageDigest {
			var copy:SHA0 = new SHA0();
			
			copy.state[0] = this.state[0];
			copy.state[1] = this.state[1];
			copy.state[2] = this.state[2];
			copy.state[3] = this.state[3];
			copy.state[4] = this.state[4];
			
			copy.counter = this.counter;
			
			for( var i:int = 0; i<this.buffer.length; i++ ) {
				copy.buffer[i] = this.buffer[i];
			}
			
			return copy;
		}
		
		public function hashUpdate( data:ByteArray ):void {
			// Break up into 64 byte chunks.
			var i:int=0;
			
			while( i<data.length ) {
				if( data.length - i + buffer.length >= BLOCK_SIZE ) {
					var usedBytes:int = BLOCK_SIZE-buffer.length;
					buffer.writeBytes( data, i, usedBytes );
					buffer.position=0;
					internalHashUpdate( buffer );
					counter += BLOCK_SIZE << 3;
					buffer.length=0;
					buffer.position=0;
					i+= usedBytes;
				} else {
					// Save remaining bytes for next chunk
					buffer.writeBytes( data, i, data.length-i );
					i += data.length-i;
				}
			}
		}
		
		public function hashFinal( data:ByteArray ):ByteArray {
			if( data == null ) {
				data = new ByteArray();
			}
			
			// Consume up to the last block
			hashUpdate( data );
			counter += buffer.length << 3;

			// Append the bits 1000 0000
			buffer[buffer.length] = 0x80;
			
			// See if we have enough room to pad out the final block
			if( buffer.length > BLOCK_SIZE-8 ) {			
				while( buffer.length < BLOCK_SIZE ) {
					buffer[buffer.length]=0x0;
				}
				internalHashUpdate( buffer );
				buffer.position=0;
				buffer.length=0;
				
				// Write a zero buffer.
				for( var i:int = 0; i<BLOCK_SIZE-8; i++ ) {
					buffer[i] = 0;
				}
			}
			
			// Expand the buffer out to a block size
			while( buffer.length < BLOCK_SIZE ) {
				buffer[buffer.length] = 0x0;
			}
			
			// Append the bit count (8 bytes) to buffer
			var countStr:String = counter.toString(16);
			var countBytes:ByteArray = new BigInteger( countStr, 16, true ).toByteArray();
			var zero:int = countBytes[0];
			if( countBytes.length > 8 ) {
				throw new ArgumentError( "counter overflow" );
			}
			for( var j:int = 0; j<countBytes.length; j++ ) {
				buffer[BLOCK_SIZE-countBytes.length+j] = countBytes[j];
			}
			
			// Process the final block
			internalHashUpdate( buffer );
			
			var output:ByteArray = new ByteArray();
			output.endian = Endian.BIG_ENDIAN;
			output.writeUnsignedInt( state[0] );
			output.writeUnsignedInt( state[1] );
			output.writeUnsignedInt( state[2] );
			output.writeUnsignedInt( state[3] );
			output.writeUnsignedInt( state[4] );
			
			output.position = 0;
			return output;
		}
		
		private function internalHashUpdate( data:ByteArray ):void {
			// Expand the buffer into an array of uints
			if( data.endian != Endian.BIG_ENDIAN ) {
				throw new ArgumentError( "byte array must be big endian" );
			}
			
			var nblk:Array = new Array();
			for( var i:int = 0; i<16; i++ ) {
				nblk[i] = data.readUnsignedInt();
			}
			
			// Expand into an array of 80 uints
			for( i = 16; i<80; i++ ) {
				nblk[i] = nblk[i-3] ^ nblk[i-8] ^ nblk[i-14] ^ nblk[i-16];
			}
			
			// Do the rounds
			var a:uint = state[0];
            var b:uint = state[1];
            var c:uint = state[2];
            var d:uint = state[3];
            var e:uint = state[4];
            
	        e += ((a << 5)|(a >>> 27)) + f1(b, c, d) + nblk[0];  b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f1(a, b, c) + nblk[1];  a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f1(e, a, b) + nblk[2];  e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f1(d, e, a) + nblk[3];  d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f1(c, d, e) + nblk[4];  c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f1(b, c, d) + nblk[5];  b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f1(a, b, c) + nblk[6];  a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f1(e, a, b) + nblk[7];  e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f1(d, e, a) + nblk[8];  d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f1(c, d, e) + nblk[9];  c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f1(b, c, d) + nblk[10]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f1(a, b, c) + nblk[11]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f1(e, a, b) + nblk[12]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f1(d, e, a) + nblk[13]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f1(c, d, e) + nblk[14]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f1(b, c, d) + nblk[15]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f1(a, b, c) + nblk[16]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f1(e, a, b) + nblk[17]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f1(d, e, a) + nblk[18]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f1(c, d, e) + nblk[19]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f2(b, c, d) + nblk[20]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f2(a, b, c) + nblk[21]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f2(e, a, b) + nblk[22]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f2(d, e, a) + nblk[23]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f2(c, d, e) + nblk[24]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f2(b, c, d) + nblk[25]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f2(a, b, c) + nblk[26]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f2(e, a, b) + nblk[27]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f2(d, e, a) + nblk[28]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f2(c, d, e) + nblk[29]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f2(b, c, d) + nblk[30]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f2(a, b, c) + nblk[31]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f2(e, a, b) + nblk[32]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f2(d, e, a) + nblk[33]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f2(c, d, e) + nblk[34]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f2(b, c, d) + nblk[35]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f2(a, b, c) + nblk[36]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f2(e, a, b) + nblk[37]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f2(d, e, a) + nblk[38]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f2(c, d, e) + nblk[39]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f3(b, c, d) + nblk[40]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f3(a, b, c) + nblk[41]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f3(e, a, b) + nblk[42]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f3(d, e, a) + nblk[43]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f3(c, d, e) + nblk[44]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f3(b, c, d) + nblk[45]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f3(a, b, c) + nblk[46]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f3(e, a, b) + nblk[47]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f3(d, e, a) + nblk[48]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f3(c, d, e) + nblk[49]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f3(b, c, d) + nblk[50]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f3(a, b, c) + nblk[51]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f3(e, a, b) + nblk[52]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f3(d, e, a) + nblk[53]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f3(c, d, e) + nblk[54]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f3(b, c, d) + nblk[55]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f3(a, b, c) + nblk[56]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f3(e, a, b) + nblk[57]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f3(d, e, a) + nblk[58]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f3(c, d, e) + nblk[59]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f4(b, c, d) + nblk[60]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f4(a, b, c) + nblk[61]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f4(e, a, b) + nblk[62]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f4(d, e, a) + nblk[63]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f4(c, d, e) + nblk[64]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f4(b, c, d) + nblk[65]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f4(a, b, c) + nblk[66]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f4(e, a, b) + nblk[67]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f4(d, e, a) + nblk[68]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f4(c, d, e) + nblk[69]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f4(b, c, d) + nblk[70]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f4(a, b, c) + nblk[71]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f4(e, a, b) + nblk[72]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f4(d, e, a) + nblk[73]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f4(c, d, e) + nblk[74]; c =((c << 30)|(c >>> 2));
	        e += ((a << 5)|(a >>> 27)) + f4(b, c, d) + nblk[75]; b =((b << 30)|(b >>> 2));
	        d += ((e << 5)|(e >>> 27)) + f4(a, b, c) + nblk[76]; a =((a << 30)|(a >>> 2));
	        c += ((d << 5)|(d >>> 27)) + f4(e, a, b) + nblk[77]; e =((e << 30)|(e >>> 2));
	        b += ((c << 5)|(c >>> 27)) + f4(d, e, a) + nblk[78]; d =((d << 30)|(d >>> 2));
	        a += ((b << 5)|(b >>> 27)) + f4(c, d, e) + nblk[79]; c =((c << 30)|(c >>> 2));

			// Update state
			state[0] += a;
			state[1] += b;
			state[2] += c;
			state[3] += d;
			state[4] += e;
		}
		
		private function f1( a:uint, b:uint, c:uint ):uint {
			return (c^(a&(b^c))) + constants[0];
		}
		
		private function f2( a:uint, b:uint, c:uint ):uint {
			return (a^b^c) + constants[1];
		}
		
		private function f3( a:uint, b:uint, c:uint ):uint {
			return ((a&b)|(c&(a|b))) + constants[2];
		}
		
		private function f4( a:uint, b:uint, c:uint ):uint {
			return (a^b^c) + constants[3];
		}
		
		
		
	}
}